#include "GfxDriver/WGL/WGLGfxDriver.h"

#include "GfxDriver/WGL/WGLHelper.h"

#include "GfxDriver/WGL/WGLShader.h"
#include "GfxDriver/WGL/WGL2DTexture.h"
#include "GfxDriver/WGL/WGLCubeTexture.h"
#include "GfxDriver/WGL/WGLRenderedTexture.h"
#include "GfxDriver/WGL/WGLVertexBuffer.h"
#include "GfxDriver/WGL/WGLVertexInput.h"
#include "GfxDriver/WGL/WGLIndexBuffer.h"
#include "GfxDriver/WGL/WGLDepthBuffer.h"
#include "GfxDriver/WGL/WGLRenderTarget.h"

#include "Core/LogService.h"
#include "Core/VecMath.h"

#include <cassert>

namespace Orca {

	//--------------------------------------------------------------------------
	ORCA_IMPLEMENT_SERVICE(GfxDriver)

	//--------------------------------------------------------------------------
	WGLGfxDriver::WGLGfxDriver() {
		mProjMtx = mViewMtx = mWorldMtx = identity4();
	}

	//--------------------------------------------------------------------------
	WGLGfxDriver::~WGLGfxDriver() {

	}

	//--------------------------------------------------------------------------
	bool WGLGfxDriver::startup() {

		// GLEW
		GLenum err = glewInit();
		
		LOG_MSG("WGLGfxDriver: starting up GLEW...");

		if(GLEW_OK != err) {
			LOG_ERR("WGLGfxDriver: failed to init GLEW");
			return false;
		}

		LOG_MSG(
			"WGLGfxDriver: using GLEW version " + Str( (const char*)glewGetString(GLEW_VERSION) ) );

		// default render state
		glFrontFace(GL_CW);

		return true;
	}
	
	//--------------------------------------------------------------------------
	void WGLGfxDriver::shutdown() {

	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::step(float dt) {

	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::resetErrors() {

		while( GL_NO_ERROR != glGetError() );
	}
	
	//--------------------------------------------------------------------------
	void WGLGfxDriver::clearFrameBuf(const vec4 &value) {
		glClearColor(value[0], value[1], value[2], value[3]);
		glClear(GL_COLOR_BUFFER_BIT);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::clearDepthBuf(float value) {
		glClearDepth(value);
		glClear(GL_DEPTH_BUFFER_BIT);
	}
	
	//--------------------------------------------------------------------------
	void WGLGfxDriver::clearStencilBuf(uint32 value) {
		glClearStencil( (GLint)value );
		glClear(GL_STENCIL_BUFFER_BIT);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::clearAllBufs(const vec4 &clr, float depth, uint32 stencil) {

		glClearColor(clr[0], clr[1], clr[2], clr[3]);
		glClearDepth(depth);
		glClearStencil( (GLint)stencil );
		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

	}

	//--------------------------------------------------------------------------
	VertexProgram* WGLGfxDriver::createVertexProgram(const uint8* data) {
		return WGLVertexProgram::create(this, data);
	}
	
	//--------------------------------------------------------------------------
	FragmentProgram* WGLGfxDriver::createFragmentProgram(const uint8* data) {
		return WGLFragmentProgram::create(this, data);
	}

	//--------------------------------------------------------------------------
	Shader* WGLGfxDriver::createShader(VertexProgram *vp, FragmentProgram *fp) {
		return WGLShader::create(this, vp, fp);
	}

	//--------------------------------------------------------------------------
	Texture2d* WGLGfxDriver::create2DTexture(uint32 w, uint32 h, PixelFormat format) {
		return WGL2DTexture::create(this, w, h, format);
	}

	//--------------------------------------------------------------------------
	TextureCube* WGLGfxDriver::createCubeTexture(uint32 size, PixelFormat format) {
		return WGLCubeTexture::create(this, size, format);
	}

	//--------------------------------------------------------------------------
	TextureRendered* WGLGfxDriver::createRenderedTexture(uint32 w, uint32 h, PixelFormat format) {
		return WGLRenderedTexture::create(this, w, h, format);
	}

	//--------------------------------------------------------------------------
	VertexBuffer* WGLGfxDriver::createVertexBuffer(uint32 sizeInBytes, bool staticUsage) {
		return WGLVertexBuffer::create(this, sizeInBytes, staticUsage);
	}

	//--------------------------------------------------------------------------
	VertexInput* WGLGfxDriver::createVertexInput() {
		return WGLVertexInput::create(this);
	}

	//--------------------------------------------------------------------------
	IndexBuffer* WGLGfxDriver::createIndexBuffer(uint32 sizeInBytes, bool staticUsage) {
		return WGLIndexBuffer::create(this, sizeInBytes, staticUsage);
	}

	//--------------------------------------------------------------------------
	RenderTarget* WGLGfxDriver::createRenderTarget() {

		return WGLRenderTarget::create(this);
	}

	//--------------------------------------------------------------------------
	DepthBuffer* WGLGfxDriver::createDepthBuffer(
			 uint32 width
			,uint32 height
			,uint32 depthBits
			,uint32 stencilBits) {

		return WGLDepthBuffer::create(this, width, height, depthBits, stencilBits);
	}

	//--------------------------------------------------------------------------
	bool WGLGfxDriver::bindRenderTarget(RenderTarget *renderTarget) {

		if(!renderTarget) {

			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

			return true;
		}
		else {

			return renderTarget->makeCurrent();
		}
	}

	//--------------------------------------------------------------------------
	bool WGLGfxDriver::bindTexture(uint32 channel, Texture *tex) {

		glActiveTexture( (GLenum)(GL_TEXTURE0 + channel) );

		if(tex) {

			GLenum texType = (GLenum)tex->getAPITexType();
			glBindTexture( texType, (GLuint)tex->getAPIHandle() );
			glEnable( texType );

		} else {

			glBindTexture( GL_TEXTURE_2D, 0 );
			glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
			glBindTexture( GL_TEXTURE_3D, 0 );
			glDisable(GL_TEXTURE_2D);
			glDisable(GL_TEXTURE_CUBE_MAP);
			glDisable(GL_TEXTURE_3D);
		}

		return true;
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setViewport(uint32 x, uint32 y, uint32 width, uint32 height) {
		glViewport((GLint)x, (GLint)y, (GLsizei)width, (GLsizei)height);
	}

	//--------------------------------------------------------------------------
	bool WGLGfxDriver::beginRender() {
		return true;
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::endRender() {
		glFlush();
	}

	//--------------------------------------------------------------------------
	bool WGLGfxDriver::beginDrawing(VertexInput *vertexInput) {

		resetErrors();

		assert(vertexInput);
		vertexInput->bind();

		return GL_NO_ERROR == glGetError();
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::draw(PrimitiveType primType, uint32 startVertex, uint32 primCnt) {

		glDrawArrays(
			WGLHelper::getGLPrimitiveType(primType),
			(GLint)startVertex,
			WGLHelper::getPrimitiveVertexCnt(primType, primCnt) );
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::drawIndexed(PrimitiveType primType, uint32 startIndex, uint32 primCnt) {

		GLsizei elemCnt = WGLHelper::getPrimitiveVertexCnt(primType, primCnt);

		glDrawRangeElements(
			WGLHelper::getGLPrimitiveType(primType),
			(GLuint)startIndex,
			(GLuint)startIndex + elemCnt,
			elemCnt,
			mCurrIndexType,
			0);
	}
	
	//--------------------------------------------------------------------------
	void WGLGfxDriver::endDrawing() {

		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);

		for(uint32 i = 0; i < GL_MAX_TEXTURE_COORDS; ++i) {

			glClientActiveTexture( GLenum(GL_TEXTURE0 + i) );
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setCurrentGLIndexType(GLenum indexType) {

		mCurrIndexType = indexType;
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setCullMode(CullMode cullMode) {

		switch(cullMode) {
			case CULL_NONE:
			{
				glDisable(GL_CULL_FACE);
				break;
			}

			case CULL_CW:
			{
				glCullFace(GL_FRONT);
				glEnable(GL_CULL_FACE);
				break;
			}
			case CULL_CCW:
			{
				glCullFace(GL_BACK);
				glEnable(GL_CULL_FACE);
				break;
			}
		}
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setDepthTestEnable(bool enable) {

		if(enable)
			glEnable(GL_DEPTH_TEST);
		else
			glDisable(GL_DEPTH_TEST);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setDepthTestFunc(CmpFunc cmp) {

		glDepthFunc( WGLHelper::getGLCmpFunc(cmp) );
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setDepthWriteEnable(bool enable) {

		glDepthMask( enable );
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setAlphaBlendEnable(bool enable) {

		if(enable)
			glEnable(GL_BLEND);
		else
			glDisable(GL_BLEND);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setSeperatedAlphaBlendEnable(bool enable) {

	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setAlphaBlendColorFactor(BlendFactor srcFactor, BlendFactor destFactor) {

		mSrcClrBlend  = WGLHelper::getGLBlendFactor(srcFactor);
		mDestClrBlend = WGLHelper::getGLBlendFactor(destFactor);

		glBlendFunc(mSrcClrBlend, mDestClrBlend);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setAlphaBlendAlphaFactor(BlendFactor srcFactor, BlendFactor destFactor) {

		glBlendFuncSeparate(
			  mSrcClrBlend
			, mDestClrBlend
			, WGLHelper::getGLBlendFactor(srcFactor)
			, WGLHelper::getGLBlendFactor(destFactor) );
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setAlphaTestEnable(bool enable) {

		if(enable)
			glEnable(GL_ALPHA_TEST);
		else
			glDisable(GL_ALPHA_TEST);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setAlphaTestFunc(CmpFunc cmp, float refValue) {

		glAlphaFunc(WGLHelper::getGLCmpFunc(cmp), refValue);
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setWorldTransform(const mtx44 &mtx) {
		mWorldMtx = mtx;
		_updateGLModelViewMtx();
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setViewTransform(const mtx44 &mtx) {
		mViewMtx = mtx;
		_updateGLModelViewMtx();
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::setProjTransform(const mtx44 &mtx) {
		mProjMtx = mtx;
		_updateGLProjMtx();
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::_updateGLModelViewMtx() {

		mtx44 modelViewMtx = mViewMtx * mWorldMtx;

		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf( modelViewMtx.getData() );
	}

	//--------------------------------------------------------------------------
	void WGLGfxDriver::_updateGLProjMtx() {

		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf( mProjMtx.getData() );
	}

	//--------------------------------------------------------------------------
}